/*
 Copyright (c) [Year] [name of copyright holder]
 [Software Name] is licensed under Mulan PSL v2.
 You can use this software according to the terms and conditions of the Mulan
 PSL v2.
 You may obtain a copy of Mulan PSL v2 at:
          http://license.coscl.org.cn/MulanPSL2
 THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY
 KIND, EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO
 NON-INFRINGEMENT, MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
 See the Mulan PSL v2 for more details.
*/

#include "PasteIO.hpp"

/**
 * @brief 获取指定目录下的文件路径
 *
 * @param filesOut 输出的文件路径向量，将存储获取到的文件路径
 * @param dir 要获取文件路径的目录
 * @param extension 文件扩展名，如果为空则表示接受所有文件
 * @param isRecursive 是否递归遍历子目录
 *
 * 获取指定目录下的文件路径
 * 如果指定了扩展名，则只获取具有该扩展名的文件
 * 如果设置为递归模式，则会遍历子目录
 */
void PasteIO::GetFiles(std::vector<fs::path> &filesOut, const std::string &dir, const std::string &extension,
                       bool isRecursive) {
    try {
        // 检查目录路径是否有效
        if (dir.empty() || !fs::is_directory(dir) || !fs::exists(dir)) {
            throw std::runtime_error("[PasteIO::GetFiles]目录路径无效: " + dir);
        }
        // 处理扩展名字符串，确保其格式正确
        std::string ext = extension;
        if (!ext.empty()) {
            std::transform(ext.begin(), ext.end(), ext.begin(), ::tolower);
            if (ext[0] != '.') {
                ext = "." + ext;
            }
        }
        // 清空输出向量以准备存储新的文件路径
        filesOut.clear();
        // 定义一个lambda函数来过滤文件，根据扩展名筛选文件
        auto filterFile = [ext](const fs::directory_entry &entry) -> bool {
            // 如果不是常规文件，则忽略
            if (!entry.is_regular_file()) {
                return false;
            }
            // 如果指定了扩展名，则根据文件扩展名过滤
            if (!ext.empty()) {
                std::string fileExt = entry.path().extension().string();
                std::transform(fileExt.begin(), fileExt.end(), fileExt.begin(), ::tolower);
                return fileExt == ext;
            }
            // 如果没有指定扩展名，则接受所有文件
            return true;
        };

        // 根据是否递归的标志来选择合适的遍历方式
        if (isRecursive) {
            // 递归遍历目录并应用过滤函数
            for (auto &p : fs::recursive_directory_iterator(dir)) {
                if (filterFile(p)) {
                    filesOut.push_back(p.path());
                }
            }
        } else {
            // 非递归遍历目录并应用过滤函数
            for (auto &p : fs::directory_iterator(dir)) {
                if (filterFile(p)) {
                    filesOut.push_back(p.path());
                }
            }
        }
    } catch (const std::exception &e) { // 捕获其他标准异常，输出错误信息
        throw std::runtime_error("[PasteIO::GetFiles]错误: " + std::string(e.what()) + " 路径: " + dir);
    }
}

/**
 * @brief 将文本文件的内容读取到字符串列表中
 *
 * @param strListOut 输出参数，包含从文件中读取的字符串列表
 * @param filePath 文件路径
 * @param mode 文件打开模式
 *
 * 此函数尝试打开指定路径的文本文件，并将每一行内容读取到一个字符串列表中
 * 它会忽略行首和行尾的空白字符，并且如果一行在去除空白字符后为空，则忽略该行
 * 如果文件无法打开或读取过程中发生错误，输出错误信息
 */
void PasteIO::TextToList(std::vector<std::string> &strListOut, const std::string &filePath, std::ios_base::openmode mode) {
    if (filePath.empty()) {
        throw std::invalid_argument("[PasteIO::TextToList]文件路径不能为空");
    }
    try {
        // 尝试以指定模式打开文件
        std::ifstream fileStream(filePath, mode);
        if (!fileStream.is_open()) {
            // 如果文件无法打开，输出错误信息
            throw std::runtime_error("[PasteIO::TextToList]文件打开失败 路径: " + filePath);
        }

        std::string line;
        // 逐行读取文件内容
        while (std::getline(fileStream, line)) {
            // 去除行首和行尾的空白字符
            std::size_t left = line.find_first_not_of(" \n\r\t");
            std::size_t right = line.find_last_not_of(" \n\r\t");

            if (left == std::string::npos || right == std::string::npos) {
                continue;
            }
            // 如果去除空白字符后的行不为空，则添加到输出列表中
            line = line.substr(left, right - left + 1);
            if (!line.empty()) {
                strListOut.push_back(line);
            }
        }
        // 显式关闭文件流
        fileStream.close();
    } catch (const std::exception &e) { // 捕获其他标准异常，输出错误信息
        throw std::runtime_error("[PasteIO::TextToList]错误: " + std::string(e.what()) + " 路径: " + filePath);
    }
}

/**
 * @brief 将指定文件的文本内容读取到字符串变量中
 *
 * @param strOut 输出参数，将读取的文本内容存储在此变量中
 * @param filePath 输入参数，指定要读取文本的文件路径
 * @param mode 文件打开模式，例如标准输入输出流的打开方式
 *
 * 此函数打开指定路径的文件，并将文件中的所有文本内容读取到textOut参数中。
 * 如果文件无法打开或读取过程中发生错误，textOut将保持不变。
 */
void PasteIO::TextToString(std::string &strOut, const std::string &filePath, std::ios_base::openmode mode) {
    // 检查文件路径是否为空，如果为空则抛出无效参数异常
    if (filePath.empty()) {
        throw std::invalid_argument("[PasteIO::TextToString]文件路径不能为空");
    }
    try {
        // 尝试打开指定路径的文件
        std::ifstream fileStream(filePath, mode);
        // 如果文件未能成功打开，则抛出运行时错误异常
        if (!fileStream.is_open()) {
            throw std::runtime_error("文件打开失败");
        }
        // 使用ostringstream读取文件内容到内存中
        std::ostringstream textStream;
        textStream << fileStream.rdbuf();
        // 将读取的文件内容赋值给输出参数textOut
        strOut = textStream.str();
        // 关闭文件流
        fileStream.close();
    } catch (const std::exception &e) { // 捕获异常，输出错误信息
        throw std::runtime_error("[PasteIO::TextToString]错误: " + std::string(e.what()) + " 路径: " + filePath);
    }
}

/**
 * @brief 验证给定路径的有效性
 *
 * @param path 待验证的文件或目录路径
 * @param isDir 指示路径是否为目录的布尔值
 *
 * 验证给定路径是否有效，如果路径不为空且符合命名规则，则不执行任何操作；否则抛出异常。
 * 如果路径是要创建的文件的路径，则会确保其父目录存在，如果父目录不存在，则会尝试创建。
 */
void PasteIO::VerifyDir(const std::string &path, bool isDir) {
    // 检查路径是否为空，如果为空，则抛出无效参数异常。
    if (path.empty()) {
        throw std::invalid_argument("[PasteIO::VerifyPath] 路径不能为空");
    }
    // 检查路径是否包含非法字符，如果不符合命名规则，则抛出无效参数异常。
    if (!std::regex_match(path, std::regex("^[a-zA-Z0-9:/\\(\\)\\\\._-]+$"))) {
        throw std::invalid_argument("[PasteIO::VerifyPath] 路径包含非法字符" + path);
    }
    fs::path fileDir(path);
    // 如果路径不是目录，则获取其父目录。
    if (!isDir) {
        fileDir = fileDir.parent_path();
    }
    // 尝试检查目录是否存在，如果不存在，则尝试创建。
    try {
        if (!fs::exists(fileDir)) {
            fs::create_directories(fileDir);
        }
    } catch (const std::exception &e) { // 捕获其他类型的异常，并重新抛出运行时错误异常。
        throw std::runtime_error("[PasteIO::VerifyPath] 错误: " + std::string(e.what()) + " 路径: " + path);
    }
}

// 错误日志的互斥锁
static std::mutex errorMutex;

/**
 * @brief 将错误信息写入标准错误流,按任意建退出程序
 *
 * @param errorStr 错误信息字符串
 *
 * 此函数将错误信息写入标准错误流，并添加一个换行符
 */
void PasteIO::WriteError(const std::string &errorStr) {
    // 使用互斥锁确保线程安全，防止多个线程同时写入错误日志
    std::lock_guard<std::mutex> lock(errorMutex);

    // 打开或创建error.log文件，以附加模式写入数据
    std::ofstream errorFile("error.log", std::ios::app);

    // 检查文件是否成功打开，如果未打开则输出错误信息并返回
    if (!errorFile.is_open()) {
        std::cerr << errorStr << "[PasteIO::WriteError] 无法打开[error.log]文件，请检查文件权限" << std::endl;
        return;
    }

    // 获取当前系统时间，用于错误日志的时间戳
    auto now = std::chrono::system_clock::now();
    auto time = std::chrono::system_clock::to_time_t(now);

    // 将错误信息连同时间戳一起写入日志文件
    errorFile << "[" << std::put_time(std::localtime(&time), "%Y-%m-%d %H:%M:%S") << "] - " << errorStr << std::endl;

    // 检查写入操作是否成功
    if (errorFile.fail()) {
        // 如果写入失败，则输出错误信息到标准错误流
        std::cerr << errorStr << "[PasteIO::WriteError] 写入错误信息到[error.log]文件失败，请检查磁盘空间" << std::endl;
    } else {
        // 如果写入成功，则输出成功信息到标准错误流
        std::cerr << errorStr << "[PasteIO::WriteError] 错误信息已保存至程序目录[error.log]文件中" << std::endl;
    }
    errorFile.close();
    std::cout << "按任意键退出程序!" << std::endl;
    std::cin.get();
    std::cin.get();
    exit(0);
}

/**
 * 将字符串内容保存到指定文件中
 *
 * @param saveStrStream 包含要保存字符串的stringstream对象
 * @param filePath 要保存到的文件路径
 * @param mode 文件打开模式，可选，默认为二进制读写
 *
 * 此函数负责将内存中的字符串内容写入到指定路径的文件中使用指定的文件打开模式
 * 如果文件路径不存在，函数将尝试创建新文件并写入内容
 * 如果文件路径已存在，根据提供的文件打开模式，函数将覆盖或附加内容到文件中
 */
void PasteIO::SaveStringToFile(const std::stringstream &saveStrStream, const std::string &filePath,
                               std::ios_base::openmode mode) {
    // 创建一个ofstream对象来写入文件
    std::ofstream file(filePath, mode);

    // 检查文件是否成功打开
    if (!file.is_open()) {
        // 如果文件打开失败，抛出异常，包含错误信息和文件路径
        throw std::runtime_error("[PasteIO::SaveStringToFile] 错误: 文件打开失败" + filePath);
    }
    try {
        // 将stringstream的内容写入文件
        // 使用rdbuf()方法将整个缓冲区的内容写入文件，这样可以高效地传输大量数据
        file << saveStrStream.rdbuf();
    } catch (const std::exception &e) {
        // 捕获写入过程中可能发生的异常
        // 确保在发生异常时关闭文件，防止资源泄露
        file.close();
        // 重新抛出异常，包含写入失败的详细信息
        throw std::runtime_error("[PasteIO::SaveStringToFile] 错误: 写入文件失败 - 路径: " + filePath +
                                 ", 原因: " + std::string(e.what()));
    }

    // 不需要显式调用 close()，因为 ofstream 的析构函数会自动关闭文件
    // 这里依赖于对象的析构来自动管理资源的释放，提高代码的简洁性和可维护性
}

int PasteIO::MoveFileAndRename(const std::string &src, const std::string &dest) {

    fs::path source = src;
    fs::path destination = dest;

    try {
        // 移动文件
        fs::rename(source, destination);
        return 0;
    } catch (fs::filesystem_error &e) {
        std::cerr << "文件移动失败" << e.what() << std::endl;
        return 1;
    }
}